package apig.core;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveAction;
import java.util.concurrent.RecursiveTask;

import javax.management.RuntimeErrorException;

import apig.algorithm.Algorithm;
import apig.algorithm.ProgressMonitor;
import apig.algorithm.colmo.ColmogorovDiffAlgo;
import apig.algorithm.fingerprints.FingerprintsAlgorithm;

/**
 * <b>Note:</b> Thread SAFE.
 * <p>
 * <b>Note:</b> Creates threads.
 * 
 * @author alyaxey
 */
public class AlgoRunManager {
	private static final List<Algorithm> ALGOS = Arrays.asList(new Algorithm[] {
			new FingerprintsAlgorithm(), new ColmogorovDiffAlgo() });
	private final Map<Integer, RunInfo> infos = new HashMap<Integer, RunInfo>();
	private final ForkJoinPool pool = new ForkJoinPool();

	private AlgoRunManager() {
		try {
			run();
		} catch (SQLException e) {
			throw new RuntimeException(e);
		}
	}

	public synchronized void run() throws SQLException {
		try (Connection c = ServerThings.getThings().getConnection();
				Statement s = c.createStatement();) {
			ResultSet rs = s
					.executeQuery("select code_id from codes where checked = false");
			while (rs.next()) {
				if (!infos.containsKey(rs.getInt(1)))
					runCodeLabour0(rs.getInt(1));
			}
		}
	}

	public synchronized RunStatus getRunStatus(int codeId) throws SQLException {
		RunInfo info = infos.get(codeId);
		if (info != null)
			return info.getStatus();
		return isChecked(codeId) ? CHECKED_INFO : NEW_INFO;
	}

	public void waitForCurrent() throws InterruptedException {
		RunInfo infosArray[];
		synchronized (this) {
			infosArray = infos.values().toArray(new RunInfo[0]);
		}
		for (RunInfo info : infosArray)
			info.waitFor();
	}

	private static AlgoRunManager instance;

	public static final synchronized AlgoRunManager getManager() {
		if (instance == null)
			instance = new AlgoRunManager();
		return instance;
	}

	private synchronized boolean isChecked(int codeId) throws SQLException {
		try (Connection c = ServerThings.getThings().getConnection();
				Statement s = c.createStatement();) {
			ResultSet rs = s
					.executeQuery("select checked from codes where code_id = "
							+ codeId);
			if (!rs.next())
				throw new IllegalArgumentException("Code id not found");
			return rs.getBoolean(1);
		}
	}

	private synchronized void runCodeLabour0(int codeId) throws SQLException {
		Connection c = null;
		boolean ok = false;
		try {
			c = ServerThings.getThings().getConnection();
			RunInfo info = new RunInfo(c, codeId);
			infos.put(codeId, info);
			pool.execute(info);
			ok = true;
		} finally {
			if (!ok)
				if (c != null)
					c.close();
		}
	}

	private class RunInfo extends RecursiveAction {
		final Connection c;
		final int codeId;
		final CodeIds codeIds;
		final List<AlgoTask> algoTasks;

		public RunInfo(Connection c, int codeId) {
			if (c == null)
				throw new IllegalArgumentException();
			this.c = c;
			this.codeId = codeId;
			codeIds = new CodeIds(codeId);
			List<AlgoTask> list = new ArrayList<AlgoTask>();
			for (Algorithm algo : ALGOS)
				list.add(new AlgoTask(algo));
			algoTasks = Collections.unmodifiableList(list);
		}

		public void waitFor() throws InterruptedException {
			try {
				get();
			} catch (ExecutionException e) {
				throw new RuntimeException(e);
			}
		}

		public RunStatus getStatus() {
			double progress = 0;
			for (AlgoTask task : algoTasks)
				progress += task.progress;
			return new RunStatus(progress);
		}

		class AlgoTask extends RecursiveAction implements ProgressMonitor {

			final Algorithm algo;
			volatile double progress;

			@Override
			public void setProgress(double progress) {
				this.progress = progress;
			}

			public AlgoTask(Algorithm algo) {
				this.algo = algo;
			}

			@Override
			protected void compute() {
				try {
					algo.inspect(c, codeId, codeIds, this);
					progress = 1;
				} catch (SQLException e) {
					throw new RuntimeException(e);
				}
			}
		}

		@Override
		protected void compute() {
			try {
				try {
					invokeAll(algoTasks);
					try (Statement st = c.createStatement()) {
						st.execute("update codes set checked = true where code_id = "
								+ codeId);
					}
					synchronized (AlgoRunManager.this) {
						infos.remove(this);
						c.commit();
					}
				} finally {
					synchronized (AlgoRunManager.this) {
						infos.remove(this);
					}
					c.close();
				}
			} catch (Throwable th) {
				th.printStackTrace();
			}
		}
	}

	public static class RunStatus {
		private final double progress;

		public RunStatus(double progress) {
			this.progress = progress;
		}

		public double getProgress() {
			return progress;
		}
	}

	public static final RunStatus NEW_INFO = new RunStatus(0.0);
	public static final RunStatus CHECKED_INFO = new RunStatus(1.0);

	// public static void main(String[] args) throws InterruptedException {
	// AlgoRunManager.getManager().waitForCurrent();
	// }
}
